#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>

#include "stack.h"
#include <malloc.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <stdlib.h>
#include <pthread.h>

#define MAX_LINE 500
#define MAX_FILENAME_LENGTH 20

typedef struct Params_Tag {
    int all;
    int ignore_blank;
    int recursive;
    int suffix;
    int help;
    int thread_num;
    char suffix_string[20];
    int is_success;

} Params;

typedef struct Line_cnt {
    int blank_lines;
    int nblank_lines;
} linecnt;
typedef struct Line_info {
    char *fileName;
    linecnt line_cnt;
} line_info;

typedef struct Cnt_line_thread_fn_arg {
    stack *fileName_stack;
    stack *lineinfo_stack;
    pthread_mutex_t *file_stack_lock;
    pthread_mutex_t *lineinfo_stack_lock;
} cnt_line_thread_fn_arg;


const char *short_optstring = "Abrhs:t:";

struct FileInfo {
    char *file_pathname;
} fileInfo;

static struct option long_Opts[] = {
        {"all",    no_argument,       NULL, 'A'},
        {"suffix", required_argument, NULL, 's'},
        {"help",   no_argument,       NULL, 'h'},
        {"thread", required_argument, NULL, 't'},
        {NULL, 0,                     NULL, 0}
};

void copy_string(char* p1, char* p2) {
    //指针运算符比++优先级高
    //也就是先将*p1的值给*p2，再进行++操作，i++是先赋值，后自增
    while ((*p2++ = *p1++) != '\0'){}
}

linecnt cnt_line(char *filename) {
    int nblank_lines = 0, blank_lines = 0;
    FILE *fp;
    if ((fp = fopen(filename, "r")) == NULL) {
        printf("can't open %s", filename);
        exit(1);
    }
    char buf[MAX_LINE];
    while (!feof(fp)) {
        if (!fgets(buf, MAX_LINE, fp)) {
            blank_lines++;
            continue;
        }
//        printf("%s", buf);
        if (!strcmp(buf, "\n"))
            blank_lines++;
        else
            nblank_lines++;
    }
//    printf("blank: %d, nblank: %d\n", blank_lines, nblank_lines);
    linecnt result = {blank_lines, nblank_lines};
    fclose(fp);
    return result;
}


void *cnt_line_thread_fn(void *args) {
    cnt_line_thread_fn_arg *my_args = (cnt_line_thread_fn_arg *) args;
    stack *fileName_stack = my_args->fileName_stack;
    stack *lineinfo_stack = my_args->lineinfo_stack;
    pthread_mutex_t *file_stack_lock = my_args->file_stack_lock;
    pthread_mutex_t *lineinfo_stack_lock = my_args->lineinfo_stack_lock;

    char *pathName;
    while (!isEmpty(fileName_stack)) {
        pthread_mutex_lock(file_stack_lock);
        if (isEmpty(fileName_stack)) {
            pthread_mutex_unlock(file_stack_lock);
            pthread_exit((void *) 0);
        }
        pathName = *(char **) pop(fileName_stack);
        pthread_mutex_unlock(file_stack_lock);

        linecnt cnt = cnt_line(pathName);
        line_info info = {pathName, cnt};
//        copy_string(pathName, info.fileName);
        pthread_mutex_lock(lineinfo_stack_lock);
        push(lineinfo_stack, &info);
        pthread_mutex_unlock(lineinfo_stack_lock);
    }
    pthread_exit((void *) 0);
}


void multi_thread_line_count(stack *fileName_stack, stack *lineinfo_stack, int thread_num) {
    pthread_t *tids = malloc(thread_num * sizeof(pthread_t));
    pthread_mutex_t file_stack_lock;
    pthread_mutex_t lineinfo_stack_lock;

    cnt_line_thread_fn_arg arg = {fileName_stack, lineinfo_stack, &file_stack_lock, &lineinfo_stack_lock};
    for (int i = 0; i < thread_num; i++)
        pthread_create(&tids[i], NULL, cnt_line_thread_fn, (void *) &arg);
    for (int i = 0; i < thread_num; i++) {
        void *tret;
        int err = pthread_join(tids[i], &tret);
        if (err != 0) {
            printf("thread %i join fails with exit code %ld\n", i, err);
            exit(0);
        }
    }
}


void parseParams(Params *param, int argc, char *argv[]) {
    param->all = 0;
    param->recursive = 0;
    param->ignore_blank = 0;
    param->help = 0;
    param->suffix = 0;
    param->is_success = 1;
    param->thread_num = 1;
    int options_index = 0;
    int opt;
    while ((opt = getopt_long(argc, argv, short_optstring, long_Opts, &options_index)) != EOF) {
        switch (opt) {
            case 'A':
                //    printf("A is detected\n");
                param->all = 1;
                break;
            case 'b':
                param->ignore_blank = 1;
                break;
            case 'r':
                param->recursive = 1;
                break;
            case 'h':
                param->help = 1;
                break;
            case 's':
                param->suffix = 1;
                // printf("suffix is:%s\n",optarg);
                strcpy(param->suffix_string, optarg);
                break;
            case 't':
                param->thread_num = strtol(optarg, NULL, 10);
                break;
            case '?':
                printf("?\n");
                param->is_success = -1;
                break;
            default:
                printf("unknown opt:%c", (char) opt);
        }
    }
}

int is_matchedSuffix(char *pathName, Params *param) {
    char *suffix = strrchr(pathName, '.');
    if (suffix != NULL) {
        if (strcmp(suffix + 1, param->suffix_string) == 0) {
            printf("the same");
            return 1;
        }
    }
    return 0;
}


stack *getAllFilesName(Params *param, int argc, char *argv[]) {
    stack *unSearchStack;
    stack *fileStack;
    unSearchStack = (stack *) malloc(sizeof(stack));
    fileStack = (stack *) malloc(sizeof(stack));
    initialize(unSearchStack, sizeof(fileInfo));
    initialize(fileStack, sizeof(fileInfo));

//    printf("tag1\n");

    while (optind < argc) {
        char *path = malloc(strlen(argv[optind]));
        strcpy(path, argv[optind]);
        push(unSearchStack, &path);
        optind++;
    }
    // printf("tag2\n");

    char *pathName;
    struct stat statbuf;
    while (!isEmpty(unSearchStack)) {
        pathName = *(char **) pop(unSearchStack);
        // printf("now pathName:%s\n",pathName);
        if (lstat(pathName, &statbuf) < 0) {
            printf("can't lstat this\n");
            free(pathName);
            continue;
        }
        // printf("tag2.2\n");
        if (S_ISREG(statbuf.st_mode)) {
            // printf("tag2.2.1\n");
            if (!param->suffix || param->suffix && is_matchedSuffix(pathName, param))
                push(fileStack, &pathName);
            else
                free(pathName);
        } else if (S_ISDIR(statbuf.st_mode)) {
            // printf("tag2.2.2\n");
            DIR *dp;
            struct dirent *dirp;
            if ((dp = opendir(pathName)) == NULL) {
                // printf("tag2.3.1\n");
                free(pathName);
                continue;
            }
            while ((dirp = readdir(dp)) != NULL) {
                //ignore . and .. in dir
                if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
                    continue;

                //get new pathName to record
                char *new_pathName = malloc(strlen(pathName) + strlen(dirp->d_name) + 1);
                strcpy(new_pathName, pathName);
                new_pathName[strlen(pathName)] = '/';
                strcpy(&new_pathName[strlen(pathName) + 1], dirp->d_name);
                struct stat newFile_buff;
                // printf("tag2.3.2\n");
                // printf("a new PathName is:%s\n",new_pathName);

                //determine whether to save the newFile(DIR) to stack
                if (lstat(new_pathName, &newFile_buff) < 0)
                    free(new_pathName);
                else {
                    if (S_ISDIR(newFile_buff.st_mode) && param->recursive) {
                        // printf("tag2.3.3: push in a new dir \n");
                        push(unSearchStack, &new_pathName);
                    } else if (S_ISREG(newFile_buff.st_mode) &&
                               (!param->suffix || param->suffix && is_matchedSuffix(new_pathName, param))) {
                        // printf("tag2.3.3: push in a new file \n");
                        push(fileStack, &new_pathName);
                    } else
                        free(new_pathName);
                }
            }
            free(pathName);
        } else {
            // printf("tag2.3\n");
            free(pathName);
        }
    }// finish unSearchStack
    free(unSearchStack);

    return fileStack;
}

void format_print(stack *lineinfo_stack, Params params){
    printf("line\tfile\n");
    printf("----\t----\n");
    int blank_cnt = 0, nblank_cnt = 0, total = 0;
    line_info* tmp;
    if (lineinfo_stack->top == 1){
        tmp = (line_info *)pop(lineinfo_stack);
        char *filename = tmp->fileName;
        blank_cnt = tmp->line_cnt.blank_lines;
        nblank_cnt = tmp->line_cnt.nblank_lines;
        if (params.ignore_blank)
            printf("%4d\t%s\n", nblank_cnt, filename);
        else
            printf("%4d\t%s\n", nblank_cnt+blank_cnt, filename);
    }
    while (!isEmpty(lineinfo_stack)){
        tmp = (line_info *)pop(lineinfo_stack);
        char *filename = tmp->fileName;
        blank_cnt = tmp->line_cnt.blank_lines;
        nblank_cnt = tmp->line_cnt.nblank_lines;
        int total_ = blank_cnt + nblank_cnt;
        if (params.all){
            total += total_;
            continue;
        } else if (params.ignore_blank) {
            printf("%4d\t%s\n", nblank_cnt, filename);
            total += nblank_cnt;
            continue;
        } else {
            printf("%4d\t%s\n", total_, filename);
            total += total_;
            continue;
        }
    }
    printf("%4d\ttotal\n", total);
}

int main(int argc, char *argv[]) {
    Params param;
    parseParams(&param, argc, argv);
    // printf("all: %i \n",param.all);
    // printf("count blank: %i \n",param.count_blank);
    // printf("recursive: %i \n",param.recursive);
    // printf("suffix: %i \n",param.suffix);
    // printf("suffix_string: %s \n",param.suffix_string);
    // printf("sc:%i",param.is_success);

    char *pathName = "hbc.c";
    // if(is_matchedSuffix(pathName, &param))
    //     printf("\n %s is match with suffix: %s\n",pathName, param.suffix_string);
    //
    stack *allFiles;

    allFiles = getAllFilesName(&param, argc, argv);
    stack *lineinfo_stack = (stack *) malloc(sizeof(stack));
    initialize(lineinfo_stack, sizeof(line_info));
    multi_thread_line_count(allFiles, lineinfo_stack, param.thread_num);
    format_print(lineinfo_stack, param);
//    int fileNum = allFiles->top;
//    int tmp = fileNum;
//    linecnt *fileLines = malloc(sizeof(linecnt) * fileNum);
//    while (!isEmpty(allFiles)) {
//        pathName = *(char **) pop(allFiles);
//        printf("***file: %s\n", pathName);
//        fileLines[fileNum - 1] = cnt_line(pathName);
//        free(pathName);
//        fileNum--;
//    }
//    for (int i = 0; i < tmp; i++) {
//        printf("blank: %d, nblank: %d\n", fileLines[i].blank_lines, fileLines[i].nblank_lines);
//    }
//    free(fileLines);
    return 0;
}

